<!DOCTYPE html>
<html lang="zh-cn">
  <head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="author" content="Zhou Wei <zromyk@163.com>">
  <title>基于K210的MNIST手写数字识别</title>
  <link rel="shortcut icon" href="/favicon.ico">
  <link rel="stylesheet" href="/style/html/pure.css">
  <link rel="stylesheet" href="/style/html/main.css">
  <link rel="stylesheet" href="https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.css">
  <!-- <link rel="stylesheet" href="https://apps.bdimg.com/libs/highlight.js/9.1.0/styles/default.min.css"> -->
<link rel="stylesheet" href="/style/article/highlight/default.min.css">
<link rel="stylesheet" href="/style/article/pell-1.0.6/dist/pell.css">

</head>
<body>
  <div id="menu-background"></div>
  <div id="menu">
    <div class="pure-menu pure-menu-horizontal">
  <ul class="pure-menu-list block-middle">
    <li class="pure-menu-item">
  <a class="pure-menu-heading" href="/index.html">ZROMYK</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link" href="/index.html">主页</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link" href="/public/archive/index.html">归档</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link" href="/public/download/index.html">下载</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link" href="/public/feedback/index.html">反馈</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link" href="/public/about/index.html">关于我</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link" href="https://github.com/zromyk"><i class="fa fa-github" style="font-size:32px"></i></a>
</li>

  </ul>
</div>

  </div>
  <div id="layout">
    <div class="content">
      <div id="nav">
    <div id="navigation" class="navigation">
  <ul class="pure-menu-list">
    <li class="pure-menu-item">
  <a class="pure-menu-link nav1" onclick="animateByNav()" href="#_1">开发平台</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav1" onclick="animateByNav()" href="#_2">配置模型开发环境</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav1" onclick="animateByNav()" href="#_3">模型的建立与训练</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#_4">模型的转换</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#pb">生成pb文件</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#_5">使用工具箱</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav1" onclick="animateByNav()" href="#k210">下载模型到k210</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav1" onclick="animateByNav()" href="#_6">编写单片机程序</a>
</li>

  </ul>
</div>

</div>
<div id="content-articles">
  <h1 id="基于K210的MNIST手写数字识别" class="content-subhead">基于K210的MNIST手写数字识别</h1>
  <p>
    <span>2021-02-20</span>
    <span><span class="post-category post-category-machine-learning">Machine Learning</span></span>
    <button id="button-markdownEditor" class="pure-button" onclick="markdownEditor()">启用编辑</button>
    <button id="button-save" class="pure-button" onclick="save()">保存</button>
  </p>
  <div id="content-articles-markdownEditor" style="display: none;">
    <h1>编辑 power by pell</h1>
    <div id="editor" class="pell"></div>
    <div style="margin-top:20px;">
        <h3>Text output:</h3>
        <div id="text-output"></div>
    </div>
    <div style="margin-top:20px;">
        <h3>HTML output:</h3>
        <pre id="html-output"></pre>
    </div>
  </div>
  <div id="content-articles-markdown">
    <h1 id="_1">开发平台</h1>
<p>操作系统：macOS系统<br />
模型训练：tensorflow2.1 CPU版<br />
模型转换工具：NNCase<br />
NNCase下载地址：<a href="https://github.com/kendryte/nncase/releases">Github</a>.</p>
<blockquote class="content-quote">
<p>从 <a href="https://github.com/kendryte/nncase/releases/tag/v0.2.0-alpha1">NNCase v0.2.0 Alpha1</a> 开始，生成的模型为 kmodel v4，如果遇到 K210 读取 kmodel 失败，请升级最新的 SDK</p>
</blockquote>
<h1 id="_2">配置模型开发环境</h1>
<p>打开终端进入 anoconda 使用 conda create 命令新建虚拟环境</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>conda create -n tf2.1 python=3.7
</code></span></code></pre>
<p>激活虚拟环境</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>conda activate tf2.1
</code></span></code></pre>
<p>下载安装 tensorflow2</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pip install tensorflow==2.1 -i https://pypi.doubanio.com/simple/ 
</code></span></code></pre>
<h1 id="_3">模型的建立与训练</h1>
<p>本次使用的深度学习的方法去识别手写的数字，这里采用 LeNet5 卷积神经网络进行模型的搭建。</p>
<p>首先什么是 LeNet5 呢，这张图很好的说明了网络的架构。简单来讲这个网络包含了两个卷积，池化层和两个全连接层。</p>
<p><img class="pure-img" alt="lenet5" src="https://zromyk.gitee.io/myblog-figurebed/post/基于K210的MNIST手写数字识别.assets/LeNet5.jpg" /></p>
<h2 id="_4">模型的转换</h2>
<p>因为 k210 单片机并不能识别 tensorflow 的模型，因此需要转换。大致的流程是这样的。</p>
<p><span style="overflow-x: auto; max-width:100%; display:inline;"><code>h5 –&gt; pb –&gt; tflite –&gt; kmodel</code></span></p>
<p>首先需要下载工具Maix_Toolbox：<a href="https://github.com/sipeed/Maix_Toolbox">Github</a>.</p>
<p>注意阅读 Maix<em>Toolbox 项目中的 README.md 文件，初次使用需要先执行脚本 get</em>nncase.sh，这会创建几个工作文件夹。其中 ncc 文件夹中的应用 ncc 请替换成当前系统可以执行的较新版本。</p>
<p>目前最新为：<a href="https://github.com/kendryte/nncase/releases/tag/v0.2.0-beta4">NNCase v0.2.0 Beta4</a></p>
<h2 id="pb">生成pb文件</h2>
<p>采用 tensorflow2 直接转换为 pb 文件。将它拷贝到 Maix_Toolbox/workspace 下。</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>python h52pb.py
</code></span></code></pre>
<h2 id="_5">使用工具箱</h2>
<p>进入 tf1.15 的虚拟环境</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>conda create --name tf1.15 python=3.7
</code></span></code></pre>
<p>激活虚拟环境</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>conda activate tf1.15
</code></span></code></pre>
<p>下载安装 tensorflow1</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pip install tensorflow==1.15 -i https://pypi.doubanio.com/simple/
</code></span></code></pre>
<p>并 cd 到 Maix_Toolbox 路径下，输入如下指令查看计算图。</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>python ./gen_pb_graph.py workspace/hdf52pb.pb
</code></span></code></pre>
<p>注意找到计算图的输入与输出，这里输入是 conv2d<em>input， 输出是 dense</em>2/Softmax。</p>
<p>转换为tflite文件</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>./pb2tflite.sh 
</code></span></code></pre>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>This script help you generate cmd to convert *.pb to *.tflite
Please put your pb into workspace dir
1. pb file name: (don't include workspace)
hdf52pb.pb  
2. input_arrays name:
conv2d_input
3. output_arrays name:
dense_2/Softmax
4. input width:
28
5. input height:
28
6. input channel:
1
</code></span></code></pre>
<p>根据提示依次输入对应内容即可，最终会自动生成需要的指令，并自动生成 tflite 文件。</p>
<p>最后转换为 kmodel 模型</p>
<p>首先需要准备训练集并保存到 toolbox 的 images 目录下，需要是图片格式。</p>
<p>复制所有图片到 toolbox 下的 images 文件夹下。运行以下指令：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>./ncc/ncc compile workspace/hdf52pb.tflite workspace/test1.kmodel -i tflite -o kmodel -t k210 --dataset images --dump-ir --max-allocator-solve-secs 60 --calibrate-method l2 -v
</code></span></code></pre>
<p>我们可以看到最终的转换结果和模型的大小</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>version 0.2
1. Import graph...
2. Optimize Pass 1...
3. Optimize Pass 2...
4. Quantize...
  4.1. Add quantization checkpoints...
  4.2. Get activation ranges...
  Plan buffers...
  Run calibration...
  [==================================================] 100% 42.01s
  4.3. Get activation distributions...
  Plan buffers...
  Run calibration...
  [==================================================] 100% 37.723s
  4.4. Find optimal thresholds...
{0, 1} -&gt; {0, 0.999512}                              ] 0% 0s
{0, 1.9926} -&gt; {0, 1.93033}                          ] 14% 0.025s
{0, 3.69957} -&gt; {0, 3.313}                           ] 28% 0.052s
{0, 3.35961} -&gt; {0, 2.92818}                         ] 42% 0.078s
{0, 2.2593} -&gt; {0, 2.03094}====&gt;                     ] 57% 0.103s
{0, 8.00717} -&gt; {0, 6.72868}==========&gt;              ] 71% 0.129s
{-0.417182, 0.411523} -&gt; {-0.364174, 0.358111}       ] 85% 0.154s
  [==================================================] 100% 14.779s
  4.5. Quantize graph...
5. Lowering...
6. Generate code...
  Plan buffers...
  Emit code...
Main memory usage: 21296 B

SUMMARY
INPUTS
0   Input_0 1x1x28x28
OUTPUTS
0   dense_2/Softmax 1x10
</code></span></code></pre>
<h1 id="k210">下载模型到k210</h1>
<p>使用 kflash_gui 工具把 kmodel 模型下载到 k210 的 flash 中（注意选好地址本次使用0x300000）如果 maixpy 的固件过大，调整地址，避免重叠。</p>
<h1 id="_6">编写单片机程序</h1>
<p>打开 maixpy IDE</p>
  </div>
</div>
 
    </div>
  </div>
  <div id="footer-background">
    <div id="footer">
      <div class="legal pure-g">
  <div class="pure-u-1 u-sm-1-2">
    <p class="legal-license"><a href="https://beian.miit.gov.cn/#/Integrated/index">浙ICP备2020038748号</a></p>
  </div>
  <div class="pure-u-1 u-sm-1-2">
    <p class="legal-links"><a href="https://github.com/zromyk">GitHub</a></p>
    <p class="legal-copyright">Copyright © 2021 Wei Zhou. 保留所有权利。</p>
  </div>
</div>
    </div>
  </div>
  <!-- <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script> -->
  <script src="/style/html/jquery.min.js"></script>
  <script src='/style/article/latex/latest.js?config=TeX-MML-AM_CHTML'></script>
<!-- <script src="https://cdn.geogebra.org/apps/deployggb.js"></script> -->
<script src="/style/article/deployggb.js"></script>
<!-- <script src="https://apps.bdimg.com/libs/highlight.js/9.1.0/highlight.min.js"></script> -->
<script type="text/javascript">
  // 脚本：navigation 随鼠标移动自动变换宽度
  var element = document.getElementById("navigation"); // 获取要操作的元素
  var elementWidth = parseInt(getComputedStyle(element).width);
  var elementLeft = 0;
  var elementRight = 0;
  element.addEventListener('mouseenter', function (event) { // 添加鼠标按下事件的监听器
    elementLeft = element.getBoundingClientRect().left - 10;
    elementRight = element.getBoundingClientRect().left + elementWidth * 3;
    window.addEventListener('mousemove', resize); // 添加全局的鼠标移动事件的监听器
  });

  function resize(event) {
    var minWidth = elementWidth;
    var maxWidth = elementWidth * 2.5;
    // console.log(elementLeft, event.clientX, elementRight, event.clientX - elementLeft + elementWidth / 2);
    if (elementLeft <= event.clientX && event.clientX <= elementRight) {
      var width = event.clientX - elementLeft + elementWidth / 2;
      width = Math.min(width, maxWidth);
      width = Math.max(width, minWidth);
      element.style.width = width + 'px'; // 设置新的宽度样式属性
    }
    else {
      element.style.width = elementWidth + 'px'; // 设置新的宽度样式属性
      stopResize();
    }
  }

  function stopResize() {
    element.style.width = elementWidth + 'px'; // 设置新的宽度样式属性
    // console.log("stopResize", elementLeft, event.clientX, elementRight, event.clientX - elementLeft + elementWidth / 2);
    window.removeEventListener('mousemove', resize); // 移除鼠标移动事件的监听器
  }
</script>
<script src="/style/article/highlight/highlight.min.js"></script>
<script type="text/javascript">
  // 脚本：code语法高亮
  hljs.initHighlightingOnLoad();
</script>
<script>
  function animateByNav() {
    $("html").animate({
        scrollTop: ($(event.target.hash).offset().top - 52)
    }, 300);
  };
</script>
<script src="/style/article/pell-1.0.6/dist/pell.js"></script>
<script>
  // 脚本：自由编辑页面
  var editor = window.pell.init({
    element: document.getElementById('editor'),
    defaultParagraphSeparator: 'p',
    onChange: function(html) {
        document.getElementById('text-output').innerHTML = html
        document.getElementById('html-output').textContent = html
    }
  });

  function markdownEditor() {
    var articles = document.getElementById('content-articles-markdown');
    if (articles.getAttribute("contenteditable") == "true") {
        articles.setAttribute("contenteditable", "false");
        document.getElementById("content-articles-markdownEditor").style.display = "none"; //隐藏
        document.getElementById("button-markdownEditor").innerHTML = "启用编辑";
    } else {
        articles.setAttribute("contenteditable", "true");
        document.getElementById("content-articles-markdownEditor").style.display = ""; //显示
        document.getElementById("button-markdownEditor").innerHTML = "关闭编辑";
    }
  };

  function save() {
      window.alert("保存成功");
  };
</script>

</body>
</html>
